/*-------------------------------------------------------------------------
 *
 * nodeCtescan.c
 *	  处理 CteScan 节点的例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeCtescan.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "executor/execdebug.h"
#include "executor/nodeCtescan.h"
#include "miscadmin.h"

static TupleTableSlot *fc_CteScanNext(CteScanState *fc_node);

/* ----------------------------------------------------------------
 *		CteScanNext
 *
 *		这是 ExecCteScan 的工作马
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_CteScanNext(CteScanState *fc_node)
{
	EState	   *fc_estate;
	ScanDirection fc_dir;
	bool		fc_forward;
	Tuplestorestate *fc_tuplestorestate;
	bool		fc_eof_tuplestore;
	TupleTableSlot *fc_slot;

	/*
	 * 从节点获取状态信息
	 */
	fc_estate = fc_node->ss.ps.state;
	fc_dir = fc_estate->es_direction;
	fc_forward = ScanDirectionIsForward(fc_dir);
	fc_tuplestorestate = fc_node->leader->cte_table;
	tuplestore_select_read_pointer(fc_tuplestorestate, fc_node->readptr);
	fc_slot = fc_node->ss.ss_ScanTupleSlot;

	/*
	 * 如果我们没有到达元组存储的末尾，或者正在向后移动，尝试
	 * 从元组存储中获取元组。
	 */
	fc_eof_tuplestore = tuplestore_ateof(fc_tuplestorestate);

	if (!fc_forward && fc_eof_tuplestore)
	{
		if (!fc_node->leader->eof_cte)
		{
			/*
			 * 在元组存储 EOF 处反向时，第一个
			 * gettupleslot 调用将获取最后添加的元组；但我们想
			 * 返回在此之前的那个（如果可能的话）。所以要进行额外
			 * 的获取。
			 */
			if (!tuplestore_advance(fc_tuplestorestate, fc_forward))
				return NULL;	/* 元组存储必须为空 */
		}
		fc_eof_tuplestore = false;
	}

	/*
	 * 如果我们可以从元组存储中获取另一个元组，返回它。
	 *
	 * 注意：我们在 tuplestore_gettupleslot 调用中必须使用 copy=true，
	 * 因为我们与可能在我们再次被调用之前写入
	 * 元组存储的其他节点共享元组存储。
	 */
	if (!fc_eof_tuplestore)
	{
		if (tuplestore_gettupleslot(fc_tuplestorestate, fc_forward, true, fc_slot))
			return fc_slot;
		if (fc_forward)
			fc_eof_tuplestore = true;
	}

	/*
	 * 如有必要，尝试从 CTE 查询中获取另一个行。
	 *
	 * 注意：eof_cte 状态变量的存在是为了短路 CTE 计划的进一步调用。
	 * 不幸的是，这不是可选的，因为某些计划节点类型在已经
	 * 返回 NULL 时不够健壮，无法再次调用。
	 */
	if (fc_eof_tuplestore && !fc_node->leader->eof_cte)
	{
		TupleTableSlot *fc_cteslot;

		/*
		 * 我们只能在 forward==true 时到达这里，所以不必担心
		 * 子计划将向哪个方向移动。
		 */
		fc_cteslot = ExecProcNode(fc_node->cteplanstate);
		if (TupIsNull(fc_cteslot))
		{
			fc_node->leader->eof_cte = true;
			return NULL;
		}

		/*
		 * 有一些特殊情况，子计划可能会改变哪个
		 * 元组存储读取指针处于活动状态，因此在存储获取的元组之前
		 * 确保重新选择我们的指针。
		 */
		tuplestore_select_read_pointer(fc_tuplestorestate, fc_node->readptr);

		/*
		 * 将返回的元组的副本附加到元组存储中。注意：因为
		 * 我们的读取指针肯定在 EOF 状态中，它的读取位置将
		 * 在添加的元组上向前移动。这正是我们想要的。此外，
		 * 任何其他读者将 *不会* 移过新的元组，这正是他们想要的。
		 */
		tuplestore_puttupleslot(fc_tuplestorestate, fc_cteslot);

		/*
		 * 我们必须将 CTE 查询的输出元组复制到我们的插槽中。这
		 * 是因为其他 CteScan 节点可能会在我们再次被调用之前提前
		 * 处理 CTE 查询，而我们的输出元组必须在此过程中保持不变。
		 */
		return ExecCopySlot(fc_slot, fc_cteslot);
	}

	/*
	 * 没有什么剩下……
	 */
	return ExecClearTuple(fc_slot);
}

/*
 * CteScanRecheck -- 访问方法例程以在 EvalPlanQual 中重新检查元组
 */
static bool fc_CteScanRecheck(CteScanState *fc_node, TupleTableSlot *fc_slot)
{
	
	return true;
}


/* ----------------------------------------------------------------
 *		ExecCteScan(node)
 *
 *		顺序扫描CTE并返回下一个符合条件的元组。
 *		我们调用ExecScan()例程并传入适当的访问方法函数。
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_ExecCteScan(PlanState *fc_pstate)
{
	CteScanState *fc_node = castNode(CteScanState, fc_pstate);

	return ExecScan(&fc_node->ss,
					(ExecScanAccessMtd) fc_CteScanNext,
					(ExecScanRecheckMtd) fc_CteScanRecheck);
}


/* ----------------------------------------------------------------
 *		ExecInitCteScan
 * ----------------------------------------------------------------
 */
CteScanState *
ExecInitCteScan(CteScan *fc_node, EState *fc_estate, int fc_eflags)
{
	CteScanState *fc_scanstate;
	ParamExecData *fc_prmdata;

	/* 检查不支持的标志 */
	Assert(!(fc_eflags & EXEC_FLAG_MARK));

	/*
	 * 目前我们必须强制tuplestore允许REWIND，因为
	 * 我们可能会被要求重新扫描CTE，即使上层没有
	 * 告诉我们要准备有效地这样做。这很烦人，因为这
	 * 防止了tuplestore的截断。XXX FIXME
	 *
	 * 注意：如果我们在EPQ重检查计划树中，那么很可能根本
	 * 不需要访问tuplestore，这让人更烦恼。
	 * 只要所有读取指针都会有REWIND，这样改善是没有价值的，
	 * 但如果将来改善了这个逻辑，那么这一点也应该被考虑。
	 */
	fc_eflags |= EXEC_FLAG_REWIND;

	/*
	 * CteScan不应该有任何子节点。
	 */
	Assert(outerPlan(fc_node) == NULL);
	Assert(innerPlan(fc_node) == NULL);

	/*
	 * 为节点创建新的CteScanState
	 */
	fc_scanstate = makeNode(CteScanState);
	fc_scanstate->ss.ps.plan = (Plan *) fc_node;
	fc_scanstate->ss.ps.state = fc_estate;
	fc_scanstate->ss.ps.ExecProcNode = fc_ExecCteScan;
	fc_scanstate->eflags = fc_eflags;
	fc_scanstate->cte_table = NULL;
	fc_scanstate->eof_cte = false;

	/*
	 * 找到已经初始化的CTE查询计划。
	 */
	fc_scanstate->cteplanstate = (PlanState *) list_nth(fc_estate->es_subplanstates,
													 fc_node->ctePlanId - 1);

	/*
	 * 与CTE查询相关的Param槽用于保存指向
	 * 第一个为此CTE初始化的CteScan节点的指针。
	 * 这个节点将持有所有CTE的共享状态，
	 * 特别是共享tuplestore。
	 */
	fc_prmdata = &(fc_estate->es_param_exec_vals[fc_node->cteParam]);
	Assert(fc_prmdata->execPlan == NULL);
	Assert(!fc_prmdata->isnull);
	fc_scanstate->leader = castNode(CteScanState, DatumGetPointer(fc_prmdata->value));
	if (fc_scanstate->leader == NULL)
	{
		/* 我是领导者 */
		fc_prmdata->value = PointerGetDatum(fc_scanstate);
		fc_scanstate->leader = fc_scanstate;
		fc_scanstate->cte_table = tuplestore_begin_heap(true, false, work_mem);
		tuplestore_set_eflags(fc_scanstate->cte_table, fc_scanstate->eflags);
		fc_scanstate->readptr = 0;
	}
	else
	{
		/* 不是领导者 */
		/* 创建我自己的读取指针，并确保它在开始位置 */
		fc_scanstate->readptr =
			tuplestore_alloc_read_pointer(fc_scanstate->leader->cte_table,
										  fc_scanstate->eflags);
		tuplestore_select_read_pointer(fc_scanstate->leader->cte_table,
									   fc_scanstate->readptr);
		tuplestore_rescan(fc_scanstate->leader->cte_table);
	}

	/*
	 * 杂项初始化
	 *
	 * 为节点创建表达式上下文
	 */
	ExecAssignExprContext(fc_estate, &fc_scanstate->ss.ps);

	/*
	 * 扫描元组类型（即，我们期望在工作
	 * 表中找到的行类型）与CTE查询的结果行类型相同。
	 */
	ExecInitScanTupleSlot(fc_estate, &fc_scanstate->ss,
						  ExecGetResultType(fc_scanstate->cteplanstate),
						  &TTSOpsMinimalTuple);

	/*
	 * 初始化结果类型和投影。
	 */
	ExecInitResultTypeTL(&fc_scanstate->ss.ps);
	ExecAssignScanProjectionInfo(&fc_scanstate->ss);

	/*
	 * 初始化子表达式
	 */
	fc_scanstate->ss.ps.qual =
		ExecInitQual(fc_node->scan.plan.qual, (PlanState *) fc_scanstate);

	return fc_scanstate;
}

/* ----------------------------------------------------------------
 *		ExecEndCteScan
 *
 *		释放通过C例程分配的任何存储。
 * ----------------------------------------------------------------
 */
void ExecEndCteScan(CteScanState *fc_node)
{
	/*
	 * 释放exprcontext
	 */
	ExecFreeExprContext(&fc_node->ss.ps);

	/*
	 * 清空元组表
	 */
	if (fc_node->ss.ps.ps_ResultTupleSlot)
		ExecClearTuple(fc_node->ss.ps.ps_ResultTupleSlot);
	ExecClearTuple(fc_node->ss.ss_ScanTupleSlot);

	/*
	 * 如果我是领导者，释放tuplestore。
	 */
	if (fc_node->leader == fc_node)
	{
		tuplestore_end(fc_node->cte_table);
		fc_node->cte_table = NULL;
	}
}

/* ----------------------------------------------------------------
 *		ExecReScanCteScan
 *
 *		重新扫描关系。
 * ----------------------------------------------------------------
 */
void ExecReScanCteScan(CteScanState *fc_node)
{
	Tuplestorestate *fc_tuplestorestate = fc_node->leader->cte_table;

	if (fc_node->ss.ps.ps_ResultTupleSlot)
		ExecClearTuple(fc_node->ss.ps.ps_ResultTupleSlot);

	ExecScanReScan(&fc_node->ss);

	/*
	 * 如果需要对底层CTE进行新的扫描，则清空tuplestore。
	 * 这会隐式重置tuplestore的所有读取指针。请注意，
	 * 多个CTE节点可能会冗余地清空tuplestore；这没关系，
	 * 并且成本不高。只要有人尝试从底层CTE
	 * 读取某些内容（因此导致其chgParam被清除），
	 * 我们将停止采取这条路径。
	 */
	if (fc_node->leader->cteplanstate->chgParam != NULL)
	{
		tuplestore_clear(fc_tuplestorestate);
		fc_node->leader->eof_cte = false;
	}
	else
	{
		/*
		 * 否则，仅重置我自己的指针。底层CTE
		 * 不需要重新扫描（我们现在可以重新读取tuplestore中的内容），
		 * 或者其他人已经处理了它。
		 */
		tuplestore_select_read_pointer(fc_tuplestorestate, fc_node->readptr);
		tuplestore_rescan(fc_tuplestorestate);
	}
}
